﻿-- Author      : Kurapica
-- Create Date : 6/30/2008

--[[
    functions to build class system
--]]

do
	-- Check Version
	local version = 1
	if not IGAS:RegisterFile("IGAS.GUI.Base.Func", version) then
		return
	end
    
    local error = error
    local rawset = rawset
	local rawget = rawget
    local type = type
	local pcall = pcall
    local geterrorhandler = geterrorhandler
    local setmetatable = setmetatable
    local unpack = unpack
	local insert = table.insert
	local remove = table.remove
	local wipe = table.wipe
	local getn = table.getn
    
	local _GUI = IGAS.GUI
	local _BaseFunc = IGAS.GUI.Base.Func
	local _Widget = IGAS.GUI.Widget
    local _Enum = IGAS.GUI.Base.Enum
    
	-- ErrHandler	
	local function errorhandler(err)
		return geterrorhandler()(err)
	end
	
 	------------------------------------------------------
	--------------------- Script Handlers -----------------
	------------------------------------------------------
	local _ScriptHandler = {}
	
	--  SctiptHandler
	for _, sc in ipairs(_Enum.ScriptType) do
		if not _ScriptHandler[sc] then
			_ScriptHandler[sc] = function(self, ...)
				self.__Wrapper:Fire(sc, ...)
			end
		end
	end
	
	-- OnEvent
	local function OnEvent(self, _event, ...)
		if self[_event] then
			return self[_event](self, ...)
		end
	end
	
 	------------------------------------------------------
	--------------------- MetaTables ---------------------
	------------------------------------------------------
	local _metaT = {
		__index = function(_table, _key)
            local _Object = _table.__ObjectType
            
			-- Special key: __Childs, __StackScripts
			if _key == "__Childs" or _key == "__StackScripts"  then
				rawset(_table, _key, {})
				return rawget(_table, _key)
			end
			
			-- Property Get
			if _Object.Property and _Object.Property[_key] then
				if _Object.Property[_key]["Get"] then
					return _Object.Property[_key]["Get"](_table)
				else
					error(_key.." is write-only.",2)
				end
			end
			
			-- Frame Func
			if _Object.FuncProxy and _Object.FuncProxy[_key] then
				return _Object.FuncProxy[_key]
			end
			
			-- Child
			if _table.__Childs and type(_table.__Childs) == "table" and _table.__Childs[_key] then
				return _table.__Childs[_key]
			end
            
            -- Scripts
			if _table.__StackScripts[_key] then
				return rawget(_table.__StackScripts[_key], 0)
			end
		end,
		
		__newindex = function(_table, _key, _value)
            local _Object = _table.__ObjectType
            
			-- Special key: __Childs
			if (_key == "__Childs" or _key == "__StackScripts") and type(_value) ~= "table" then
				error(_key.." must be a table.", 2)
			end
			
			-- Property Set
			if _Object.Property and _Object.Property[_key] then
				if _Object.Property[_key]["Set"] then
					return _Object.Property[_key]["Set"](_table,_value)
				else
					error(_key.." is read-only.", 2)
				end
			end
			
			-- Child
			if _table.__Childs and type(_table.__Childs) == "table" and _table.__Childs[_key] then
				error(_key.." is a child-widget, can't be used in other way.", 2)
			end
			
			-- Scripts
			if _table["HasScript"] and _table:HasScript(_key) and _ScriptHandler[_key] then				
                if _value and type(_value) ~= "function" then
                    error("The script handler must be a function", 2)
                end
                
                if _table:GetScript(_key) and _table:GetScript(_key) ~= _ScriptHandler[_key] then
                    error("This frame has a special script handler.", 2)
                else
					if not _table.__StackScripts[_key] or type(_table.__StackScripts[_key]) ~= "table" then
						_table.__StackScripts[_key] = {}
					end
					
					rawset(_table.__StackScripts[_key], 0, _value)					
					
					-- Register Scripts
					if getn(_table.__StackScripts[_key]) > 0 or _table.__StackScripts[_key][0] then
						-- Register
						if not _table:GetScript(_key) then
							_table:SetScript(_key, _ScriptHandler[_key])
						end
					else
						-- UnRegister
						if _table:GetScript(_key) == _ScriptHandler[_key] then
							_table:SetScript(_key, nil)
						end
					end
                end
				
				return
			end
			
			-- Personal script
			if _Object.ScriptType and _Object.ScriptType[_key] then
				if _value and type(_value) ~= "function" then
					error("This is a script handler, must be a function",2)
				end
				
				if not _table.__StackScripts[_key] or type(_table.__StackScripts[_key]) ~= "table" then
					_table.__StackScripts[_key] = {}
				end
				
				rawset(_table.__StackScripts[_key], 0, _value)
				
				return
			end
            
			rawset(_table,_key,_value)			-- Other key can be set as usual	
		end,
	}

 	------------------------------------------------------
	------------------- Base Functions -------------------
	------------------------------------------------------    
	-- This is a special method. It use a table __StackScripts to store the object's script handlers.
	-- For Exp: OnClick handler
	-- object.__StackScripts["OnClick"] = {
	-- 	[0] == "function:XXXX"		-- This is the user's handler function.It will be called the last time.And can be replaced when you use this code object.OnClick = XXX.
	--	[1] == "function:XXXX"		-- This will be called the first time when OnClick is triggered.If this return true, all left handlers will not be called. Can only be set by object:StackScript(handler, func, flag)
	--	[2] == "function:XXXX"		-- This will be called the second time.If this return true, all left handlers will not be called.
	--	......
	-- }
	_BaseFunc.StackScript = function(object, handler, func, flag)
		if not object.__StackScripts[handler] or type(object.__StackScripts[handler]) ~= "table" then
			object.__StackScripts[handler] = {}
		end
		
		if flag then
			-- Clear this handler's stack
			for i = getn(object.__StackScripts[handler]), 1, -1 do
				
				remove(object.__StackScripts[handler], i)
			end
		end
		
		insert(object.__StackScripts[handler], func)
		
		-- Register Scripts
		if object["HasScript"] and object:HasScript(handler) and _ScriptHandler[handler] then
			if getn(object.__StackScripts[handler]) > 0 or object.__StackScripts[handler][0] then
				-- Register
				if not object:GetScript(handler) then
					object:SetScript(handler, _ScriptHandler[handler])
				end
			else
				-- UnRegister
				if object:GetScript(handler) == _ScriptHandler[handler] then
					object:SetScript(handler, nil)
				end
			end
		end
	end
    
    _BaseFunc.Wrapper = function(object, objectType)                
        local _UI, _Wrapper, _Type, _objectType
        
        -- Set _UI, _Wrapper
        if object.__UI or object.__Wrapper then
            if object.__UI then
                _UI = object.__UI
                _Wrapper = object
            elseif object.__Wrapper then
                _UI = object
                _Wrapper = object.__Wrapper
            end
        else
            _UI = object
            _Wrapper = {}
        end
        
        -- Check objectType
        _Type = nil
        
        _objectType = objectType or (_Wrapper["GetObjectType"] and _Wrapper:GetObjectType()) or (_UI["GetObjectType"] and _UI:GetObjectType()) or "UIObject"
        
        if _objectType then
			if type(_objectType) == "string" and _Widget[_objectType] then
				_Type = _Widget[_objectType]
			elseif type(_objectType) == "table" and _objectType["FuncProxy"] then
				_Type = _objectType
			end
		end
        
        if not _Type then
        	_Type = _Widget["UIObject"]
       	end
        
        _UI.__Wrapper = _Wrapper
        _Wrapper.__UI = _UI
        
        _Wrapper.__ObjectType = _Type
        
        setmetatable(_Wrapper, _metaT)
        
		-- Script 
		-- Special for OnEvent, Others is no need to set.
		_BaseFunc.StackScript(_Wrapper, "OnEvent", OnEvent, true)
		
        return _Wrapper
    end
    
	_BaseFunc.GetUI = function(ob)
	    return ob and (ob.__UI or ob)
	end
	
	_BaseFunc.GetWrapper = function(ob)
		if not ob or type(ob) ~= "table" or not ob.GetObjectType then
			return ob or nil
		elseif ob.__Wrapper then
            return ob.__Wrapper
		elseif not ob["IsIGASWidget"] then
			return _BaseFunc.Wrapper(ob)
        else
            return ob
		end
	end
    
    _BaseFunc.CallMethod = function(_superType, _method, ob, ...)
		if not _superType or not _method or not ob or type(_method) ~= "string" or type(ob) ~= "table" then return end
		
		if type(_superType) == "string" then
			if _Widget[_superType] then
				_superType = _Widget[_superType]
			else
				return
			end
		elseif type(_superType) == "table" then
			if not _superType["FuncProxy"] then
				return
			end
		else
			return
		end
		
        if _superType["FuncProxy"][_method] then
        	return _superType["FuncProxy"][_method](ob, ...)
        end
    end
 	------------------------------------------------------
	------------------ Argument Convert ------------------
	------------------------------------------------------    
    local function GetRet(arrV, i, maxI)
        if i < maxI then
            return arrV[i], GetRet(arrV, i+1, maxI)
        else
            return arrV[i]
        end
    end
	
	_BaseFunc.GetArgs = function(...)
        local ret = {...}
        local e = nil
		local t
		
		e, t = next(ret, e)
		while(e) do
			if t and type(t) == "table" then
				ret[e] = _BaseFunc.GetUI(t)
			end
			e, t = next(ret, e)
		end
		
		return GetRet(ret, 1, select('#', ...))
    end

    _BaseFunc.GetVals = function(...)
        local ret = {...}
        local e = nil
		local t
		
		e, t = next(ret, e)
		while(e) do
			if t and type(t) == "table" then
				ret[e] = _BaseFunc.GetWrapper(t)
			end
			e, t = next(ret, e)
		end
		
		return GetRet(ret, 1, select('#', ...))
    end
    
	------------------------------------------------------
	--------------------- Constructor --------------------
	------------------------------------------------------    
    --- Name Creator
	local function NewName(_typeN, _parent)
		local i = 1
		local name = _typeN["Name"]
		local parentF = _parent or UIParent
		
		if not name or name == "" then
			name = "Widget"
		end
		
		parentF = _BaseFunc.GetWrapper(parentF)
		
		while true do
			if parentF:GetChild(name..i) then
				i = i + 1
			else
				break
			end
		end
		
		return name..i
	end
	
    ------------------------------------------------------
	------------------ Global Function -------------------
	------------------------------------------------------	
	local function Constructor(frameType,frameName,parentFrame,...)
		local frame
		
		---------------------------------------------- Wrapper Parent ---------------------------------------------
		parentFrame = parentFrame or UIParent
		parentFrame = _BaseFunc.GetWrapper(parentFrame)
        
		---------------------------------------------- Check -------------------------------------------------------
		---- Check FrameType
		if not frameType then
			error("There must be a frame's type be set.", 2)
		elseif type(frameType) == "string" then
			if _Widget[frameType] then
				frameType = _Widget[frameType]
			else
				error("This frameType ["..frameType.."] is not exist.", 2)
			end
		elseif type(frameType) == "table" then
            if not frameType["FuncProxy"] then
				error("This frameType is invalid.", 2)
            end
		else
			error("This frameType is invalid.", 2)
		end
		
		if not frameType.New then
            error("This frameType ["..frameType.Name.."] is abstracted.", 2)
		end
		
		---- Check Parent
		if not parentFrame["AddChild"] then
			error("Can't add child to the object ["..(parentFrame.Name or tostring(parentFrame)).."].", 2)
		end
		
		---- Check FrameName
        if frameName and type(frameName) ~= "string" then
            error("name must be string.", 2)
        end
		
        if frameName and frameName ~= "" then
            -- Check the parent's childs
            if parentFrame["GetChild"] and parentFrame:GetChild(frameName) then
            	if parentFrame:GetChild(frameName)["GetObjectType"] and parentFrame:GetChild(frameName):GetObjectType() == frameType then
            		return parentFrame:GetChild(frameName)
            	else
            		error("this name ["..frameName.."] is used", 2)
            	end
            end
        else
        	frameName = NewName(frameType, parentFrame)
        end
		
		----------------------------------------------------------------- CreateFrame ---------------------------------------------
		if frameName then
			-- New Frame
			frame = frameType.New(parentFrame,...)
			
			if not frame then
				return nil
			end
	        
	      	frame = _BaseFunc.Wrapper(frame, frameType)
	        
            frame.Name = frameName
            parentFrame:AddChild(frame)
            
			return frame
		else
			return nil
		end
	end
	
	--[[
		CreateFrame
		Args:
		     frameType		string or FrameType,like "Frame" or IGAS.GUI.Frame
		     frameName		the object's name
		     parentFrame	the object's parent
	--]]
	_BaseFunc.CreateFrame = function(frameType,frameName,parentFrame,...)	
		local status, frame = pcall(Constructor, frameType, frameName, parentFrame, ...)
		
		if not status then
			errorhandler(frame)
			frame = nil
		end
		
		return frame
	end
		
	--[[
		New Widget, Used to create Widget's Constructor
		Args:
			 _WidgetInfo				Table		Contains the information about the widget
				 {				
					WidgetName				String		Widget Name
					Base                    			String      	Base Widget
					ScriptType	            			Table		ScriptType, used for design
					FuncProxy       				Table		FuncProxy
					Property		       	 	Table		Property
					New					Function	Constructor
					NotChild                Boolean     Can't be added as a child if true
				}
	--]]
	_BaseFunc.NewWidget = function(_WidgetInfo)
	    local _Base = _WidgetInfo.Base
		local _FuncProxy = _WidgetInfo.FuncProxy or {}
		local _Property = _WidgetInfo.Property or {}
		local _ScriptType = _WidgetInfo.ScriptType or {}
        local _NotChild = _WidgetInfo.NotChild or false
		
		-- Inherit from base
        if _Base then
            if type(_Base) == "string" then
                if _Widget[_Base] then
                    _Base = _Widget[_Base]
                else
                    _Base = _Widget["UIObject"]
                end
            elseif type(_Base) == "table" then
                if not _Base["FuncProxy"] then
                    _Base = _Widget["UIObject"]
                end
            else
                _Base = _Widget["UIObject"]
            end
            local _metaFunc = {__index = _Base.FuncProxy}
            local _metaPro = {__index = _Base.Property}
            setmetatable(_FuncProxy, _metaFunc)
            setmetatable(_Property, _metaPro)
        end
		
		--[[
			Frame's metatable
		]]--
		local _NewWidget = {}
        
        local index = {
        	["Name"] = _WidgetInfo.WidgetName,
			["FuncProxy"] = _FuncProxy,
			["Property"] = _Property,
			["ScriptType"] = _ScriptType,
        }
        
		if _NotChild then
			index["NotChild"] = _NotChild
		end
		
        if _Base then
            index["Base"] = _Base
        end
        
        if _WidgetInfo.New and type(_WidgetInfo.New) == "function" then
            index["New"] = _WidgetInfo.New
        end
        
		-- The other parameters will be convert to widget directly.
		for i, v in pairs(_WidgetInfo) do
			if i ~= "WidgetName" and i ~= "FuncProxy" and i ~= "Property" and i ~= "ScriptType" and i ~= "New" and i ~= "NotChild" and i ~= "Base" then
				index[i] = v
			end
		end
		
		local _metaF = {
			__index = index,
		
			__newindex = function(_table,_key,_value)
				error("This object is read-only", 2)
			end,
		}
	    
		setmetatable(_NewWidget, _metaF)
        
        return _NewWidget
	end
    
    ------------------------------------------------------
	-------------------- GUI Function --------------------
	------------------------------------------------------
    _GUI.CreateFrame = _BaseFunc.CreateFrame
    _GUI.NewWidget = _BaseFunc.NewWidget
    _GUI.GetUI = _BaseFunc.GetUI
    _GUI.GetWrapper = _BaseFunc.GetWrapper
	
	-- Register Function
	IGAS:RegisterFunction("NewFrame", function(self, ...)
		return _BaseFunc.CreateFrame(...)
	end)
	IGAS:RegisterFunction("NewWidget", function(self, ...)
		return _BaseFunc.NewWidget(...)
	end)
	IGAS:RegisterFunction("GetUI", function(self, ...)
		return _BaseFunc.GetUI(...)
	end)
	IGAS:RegisterFunction("GetWrapper", function(self, ...)
		return _BaseFunc.GetWrapper(...)
	end)
end